package kubernetes

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/golang/glog"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	authapi "eccgateway/pkg/api/auth"
	rulesv1 "eccgateway/pkg/api/rules/v1"
	"eccgateway/pkg/util"
)

var RuleControllerImpl RuleController

func init() {
	RuleControllerImpl = RuleController{BaseKubernetesController: BaseKubernetesControllerImpl, RuleEndpointController: RuleEndpointControllerImpl}
}

type RuleController struct {
	BaseKubernetesController
	RuleEndpointController
}

const (
	RuleKind = "rule"
)

func (c RuleController) List(w http.ResponseWriter, r *http.Request) {
	labelSelectorFunc := func(labelSelectors map[string]string, info *authapi.RespToken) map[string]string {
		delete(labelSelectors, groupIDKey)
		delete(labelSelectors, userIDKey)
		largestRole := authapi.GetLargestRolePermission(info.Role)
		switch largestRole {
		case authapi.OpServiceRole:
			break
		case authapi.AdminRole:
			labelSelectors[groupIDKey] = fmt.Sprintf("%v", info.Group.ID)
		case authapi.UserRole:
			labelSelectors[userIDKey] = fmt.Sprintf("%v", info.UserID)
		default:
			labelSelectors[userIDKey] = fmt.Sprintf("%v", info.UserID)
		}
		return labelSelectors
	}
	c.BaseKubernetesController.List(RuleKind, labelSelectorFunc, w, r)
}

func (c RuleController) Create(w http.ResponseWriter, r *http.Request) {
	isAllowedCreate := func(info *authapi.RespToken, largestRole authapi.RoleType, clusterGroupID, username, userID, groupID string) bool {
		return clusterGroupID == groupID || largestRole == authapi.OpServiceRole
	}

	construction := func(requestBody []byte, username, userID, groupID string) ([]byte, string, string, error) {
		rule := rulesv1.Rule{}
		err := json.Unmarshal(requestBody, &rule)
		if err != nil {
			return nil, "", "", fmt.Errorf("unmarshal request body failed, %v", err)
		}

		clusterName := ""
		if rule.Labels != nil {
			clusterName = rule.Labels[clusterNameKey]
		}
		if clusterName == "" {
			glog.Errorf("create rule failed, the rule does not specify which cluster it belongs to, user: %v/%v, rule name: %v", username, userID, rule.Name)
			return nil, "", "", fmt.Errorf("create rule failed, the rule does not specify which cluster it belongs to")
		}

		cluster, err := c.GetCluster(clusterName)
		if err != nil {
			glog.Errorf("get cluster[%v] failed, err: %v", clusterName, err)
			return nil, "", "", err
		}

		if rule.Annotations == nil {
			rule.Annotations = make(map[string]string)
		}
		rule.Annotations[creatorAnnotationKey] = username
		if rule.Labels == nil {
			rule.Labels = make(map[string]string)
		}
		rule.Labels[userIDKey] = userID
		rule.Labels[userNameKey] = username
		rule.Labels[groupIDKey] = cluster.Labels[groupIDKey]

		err = c.validResource(rule)
		if err != nil {
			glog.Errorf("valid %v failed, err: %v", RuleKind, err)
			return nil, "", "", err
		}

		newRequestBody, _ := json.Marshal(rule)
		return newRequestBody, rule.Labels[clusterNameKey], rule.Name, nil

	}
	c.BaseKubernetesController.Create(RuleKind, construction, isAllowedCreate, w, r)
}

func (c RuleController) Get(w http.ResponseWriter, r *http.Request) {
	isAllowedGet := func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool {
		return largestRole == authapi.OpServiceRole ||
			(largestRole == authapi.AdminRole && groupID == strconv.Itoa(info.Group.ID)) ||
			(largestRole == authapi.UserRole && userID == info.UserID)
	}
	c.BaseKubernetesController.Get(RuleKind, c.getResource, isAllowedGet, w, r)
}

func (c RuleController) Delete(w http.ResponseWriter, r *http.Request) {
	isAllowedDelete := func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool {
		// op_service will delete directly, so there is no need to allow
		return (largestRole == authapi.AdminRole && groupID == strconv.Itoa(info.Group.ID)) ||
			(largestRole == authapi.UserRole && userID == info.UserID)
	}
	c.BaseKubernetesController.Delete(RuleKind, c.getResource, isAllowedDelete, w, r)
}

func (c RuleController) Update(w http.ResponseWriter, r *http.Request) {
	isAllowedUpdate := func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool {
		return largestRole == authapi.OpServiceRole ||
			(largestRole == authapi.AdminRole && groupID == strconv.Itoa(info.Group.ID)) ||
			(largestRole == authapi.UserRole && userID == info.UserID)
	}
	reconstruction := func(oldResource interface{}, requestBody []byte, username, userID, groupID string) ([]byte, error) {
		oldRule, ok := oldResource.(*rulesv1.Rule)
		if !ok {
			return nil, fmt.Errorf("failed transfer old resource to %v", RuleKind)
		}
		rule := rulesv1.Rule{}
		err := json.Unmarshal(requestBody, &rule)
		if err != nil {
			return nil, fmt.Errorf("unmarshal request body failed, %v", err)
		}

		if rule.Labels == nil {
			rule.Labels = make(map[string]string)
		}
		rule.Labels[clusterNameKey] = oldRule.Labels[clusterNameKey]
		rule.Labels[userIDKey] = userID
		rule.Labels[userNameKey] = username
		rule.Labels[groupIDKey] = oldRule.Labels[groupIDKey]

		err = c.validResource(rule)
		if err != nil {
			glog.Errorf("valid %v failed, err: %v", RuleKind, err)
			return nil, err
		}

		return json.Marshal(rule)
	}
	c.BaseKubernetesController.Update(RuleKind, c.getResource, reconstruction, isAllowedUpdate, w, r)
	return
}

func (c RuleController) getResource(name, namespace string) (interface{}, string, string, string, error) {
	client := util.GetKubernetesClient()

	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/apis/rules.kubeedge.io/v1/namespaces/%s/rules/%s", kubernetesAddress, namespace, name), nil)
	if err != nil {
		glog.Errorf("construct request to get rule failed, err: %v", err)
		return nil, "", "", "", err
	}

	resp, err := client.Do(req)
	if err != nil {
		glog.Errorf("send request to get rule failed, err: %v", err)
		return nil, "", "", "", err
	}

	if resp.StatusCode == http.StatusNotFound {
		return nil, "", "", "", util.NotFoundError
	}

	requestBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		return nil, "", "", "", err
	}

	if resp.StatusCode != http.StatusOK {
		status := metav1.Status{}
		err = json.Unmarshal(requestBody, &status)
		if err != nil {
			glog.Errorf("unmarshal response body failed, rule name: %v/%v, err: %v", namespace, name, err)
			return nil, "", "", "", fmt.Errorf("get rule failed")
		}
		return nil, "", "", "", fmt.Errorf("%v", status.Message)
	}

	rule := rulesv1.Rule{}
	err = json.Unmarshal(requestBody, &rule)
	if err != nil {
		glog.Errorf("unmarshal response body failed, rule name: %v/%v, err: %v", namespace, name, err)
		return nil, "", "", "", fmt.Errorf("get rule failed")
	}

	return &rule, rule.Labels[userNameKey], rule.Labels[userIDKey], rule.Labels[groupIDKey], nil
}

var sourceTypeResourceMap = map[string][]string{
	"rest": {"path"},
	"eventbus": {"topic", "node_name"},
}

var targetTypeResourceMap = map[string][]string{
	"rest": {"resource"},
	"eventbus": {"topic"},
	"servicebus": {"path"},
}

func (c RuleController) validResource(rule rulesv1.Rule) error {
	if rule.Spec.Source == rule.Spec.Target {
		return fmt.Errorf("source and target should be different")
	}

	sourceInterface, _, _, _, err := c.RuleEndpointController.getResource(rule.Spec.Source, rule.Namespace)
	if err == util.NotFoundError {
		return fmt.Errorf("source %v not found", rule.Spec.Source)
	}
	if err != nil {
		return fmt.Errorf("failed get source %v, %v", rule.Spec.Source, err)
	}

	targetInterface, _, _, _, err := c.RuleEndpointController.getResource(rule.Spec.Target, rule.Namespace)
	if err == util.NotFoundError {
		return fmt.Errorf("target %v not found", rule.Spec.Source)
	}
	if err != nil {
		return fmt.Errorf("failed get target %v, %v", rule.Spec.Source, err)
	}

	source, ok := sourceInterface.(*rulesv1.RuleEndpoint)
	if !ok {
		return fmt.Errorf("failed transfer source to %v", RuleEndpointKind)
	}

	checkArr, ok := sourceTypeResourceMap[source.Spec.RuleEndpointType]
	if !ok {
		return fmt.Errorf("source %v type is invalid", source.Name)
	}
	for _, v := range checkArr {
		_, ok = rule.Spec.SourceResource[v]
		if !ok {
			return fmt.Errorf("source resource should include %v", v)
		}
	}

	target, ok := targetInterface.(*rulesv1.RuleEndpoint)
	if !ok {
		return fmt.Errorf("failed transfer target to %v", RuleKind)
	}
	checkArr, ok = targetTypeResourceMap[target.Spec.RuleEndpointType]
	if !ok {
		return fmt.Errorf("target %v type is invalid", target.Name)
	}
	for _, v := range checkArr {
		_, ok = rule.Spec.TargetResource[v]
		if !ok {
			return fmt.Errorf("target resource should include %v", v)
		}
	}

	return nil
}